package com.wngbms.netty.seatdemo.client;

import com.alibaba.fastjson.JSON;
import com.wngbms.netty.seatdemo.bean.OwnerInfo;
import com.wngbms.netty.seatdemo.comm.*;
import com.wngbms.netty.seatdemo.message.BaseHeader;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

@Slf4j
public class SimpleNIOClient {
    public static ClientSeatInMessage clientSeatInMessage;

    public static void sendReq() throws IOException, InterruptedException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(true);
        socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
        Socket socket = socketChannel.socket();
        socket.connect(new InetSocketAddress("127.0.0.1", 8090));

        while (!socketChannel.finishConnect()) {
            log.info("正在连接...");
        }
        log.info("连接成功...");

        new Thread(() -> {
            try {
                log.info("begin receive...");
                receiveData(socketChannel);
            } catch (IOException e) {
                log.error("", e);
            }
        }).start();

        /*for (int i = 0; i < 5; i++) {
            sendContent(socketChannel);
        }*/
        sendConnect(socketChannel);
        sleep(1000);
        sendHeartBeat(socketChannel);
        sleep(3000);
        sendOwnerSearch(socketChannel);
        sleep(3000);
        uploadFile(socketChannel);
        sleep(2000);
        sendHeartBeat(socketChannel);
        sleep(1000);
        downloadFile(socketChannel);
        sleep(1000);
        sendHeartBeat(socketChannel);
    }

    public static void receiveData(SocketChannel socketChannel) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.clear();

        int len;
        try {
            while ((len = socketChannel.read(byteBuffer)) > 0) {
//                log.info("received:{}", len);
                handleReceive(byteBuffer, socketChannel);
            }
        } catch (ClosedChannelException e) {
            log.info("连接通道关闭...");
        }
    }

    public static void handleReceive(ByteBuffer buffer, SocketChannel socketChannel) throws IOException {
        // read mode
        buffer.flip();

        int remaining;
        while ((remaining = buffer.remaining()) > 0) {
            if (clientSeatInMessage == null) {
                if (remaining < BaseHeader.HEADER_LENGTH) {
                    // write mode
                    log.info("不够消息头的长度");
                    buffer.compact();
                    return;
                }

                BaseHeader baseHeader = new BaseHeader();
                baseHeader.writeHeader(buffer);

                ClientSeatInMessage clientSeatInMessage = new ClientSeatInMessage();
                clientSeatInMessage.setBaseHeader(baseHeader);
                SimpleNIOClient.clientSeatInMessage = clientSeatInMessage;
            }

            if ((remaining = buffer.remaining()) > 0) {
                clientSeatInMessage.writeBody(buffer, remaining, socketChannel);
            }

            if (clientSeatInMessage != null
                    && clientSeatInMessage.finished()) {
                ClientSeatInMessage req = clientSeatInMessage;
                clientSeatInMessage = null;
                doBizServ(req, socketChannel);
            }
        }

        // write mode
        buffer.compact();
    }

    public static void doBizServ(ClientSeatInMessage req, SocketChannel socketChannel) throws IOException {
        BaseHeader baseHeader = req.getBaseHeader();

        CmdType cmdType = baseHeader.getCmdType();
        switch (cmdType) {
            case EVENT:
                log.info("事件信息:{}", new String(req.getBody(), StandardCharsets.UTF_8));
                break;
            case HEARTBEAT:
                log.info("接收到服务端心跳");
                break;
            case EXTRA_INFO:
                log.info("补充额外信息:{}", new String(req.getBody(), StandardCharsets.UTF_8));
                break;
            case OWNER_SEARCH:
                log.info("业主信息查询响应:{}", new String(req.getBody(), StandardCharsets.UTF_8));
                break;
            case HANGUP:
                log.info("接收到对方挂断指令:{}", new String(req.getBody(), StandardCharsets.UTF_8));
//                sleep(500);
//                log.info("关闭连接");
//                socketChannel.close();
                break;
            case BINARY_TEST_DOWNLOAD:
                log.info("文件下载测试完毕: {}", req.getFile().getAbsolutePath());
                break;
            default:
                log.info("unknown cmd type");
        }
    }

    public static void downloadFile(SocketChannel socketChannel) throws IOException {
        BaseHeader baseHeader = BaseHeader.build(MagicVersion.V1, CmdType.BINARY_TEST_DOWNLOAD, BodyType.NON, EncryptionType.NON, ReqOrResp.REQ, 0);
        byte[] header = baseHeader.getHeader();

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.clear();

        buffer.put(header);
        buffer.flip();
        socketChannel.write(buffer);
        log.info("发送下载文件请求");
    }

    // 上传文件请求
    public static void uploadFile(SocketChannel socketChannel) {
//        String filePath = "J:\\readme.txt";
        String filePath = "J:\\aDrive.exe";
//        String filePath = "J:\\mysql-5.7.17.msi";
        File file = new File(filePath);
        if (!file.exists()) {
            log.info("{}文件不存在", filePath);
            return;
        }

        try (FileChannel fileChannel = new FileInputStream(file).getChannel()) {
            ByteBuffer buffer = ByteBuffer.allocate(2048);
            buffer.clear();

            long length = file.length();
            log.info("文件长度:{}", length);

            BaseHeader baseHeader = BaseHeader.build(MagicVersion.V1, CmdType.BINARY_TEST_UPLOAD, BodyType.BINARY, EncryptionType.NON, ReqOrResp.REQ, (int) length);
            byte[] header = baseHeader.getHeader();
            buffer.put(header);

            // read mode
            buffer.flip();
            socketChannel.write(buffer);
            log.info("请求头写入完成");

            log.info("传输文件开始");
            buffer.clear();

            /*int len = 0;
            long progress = 0;
            while ((len = fileChannel.read(buffer)) > 0) {
                buffer.flip();
                // 如果配置成非阻塞模式, 有可能write返回的为0, 导致有数据没有发送到服务端
                int write = socketChannel.write(buffer);
                buffer.clear();
                progress += len;
                log.info("本次发送字节数:{}, 已处理字节数:{}, 总字节数:{}", write, progress, length);
            }*/

            // 零拷贝实现
            MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, length);
            int write = socketChannel.write(mappedByteBuffer);
            log.info("本次发送字节数:{}, 已处理字节数:{}, 总字节数:{}", write, write, length);

            log.info("传输文件完成");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 发送业主查询请求
    public static void sendOwnerSearch(SocketChannel socketChannel) throws IOException {
        OwnerInfo ownerInfo = new OwnerInfo();
        ownerInfo.setName("霍尊");
        String jsonString = JSON.toJSONString(ownerInfo);
        byte[] bytes = jsonString.getBytes(StandardCharsets.UTF_8);
        BaseHeader baseHeader = BaseHeader.build(MagicVersion.V1, CmdType.OWNER_SEARCH, BodyType.JSON, EncryptionType.NON, ReqOrResp.REQ, bytes.length);
        byte[] header = baseHeader.getHeader();

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.clear();

        buffer.put(header);
        buffer.put(bytes);
        buffer.flip();
        socketChannel.write(buffer);
        log.info("发送业主查询请求:{}", jsonString);
    }

    // 发送心跳请求
    public static void sendHeartBeat(SocketChannel socketChannel) throws IOException {
        BaseHeader baseHeader = BaseHeader.build(MagicVersion.V1, CmdType.HEARTBEAT, BodyType.NON, EncryptionType.NON, ReqOrResp.REQ, 0);
        byte[] header = baseHeader.getHeader();

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.clear();

        buffer.put(header);
        buffer.flip();
        socketChannel.write(buffer);
        log.info("发送心跳");
    }

    // 发送登录请求
    public static void sendConnect(SocketChannel socketChannel) throws IOException {
        String userId = "rxseat01";
        byte[] bytes = userId.getBytes(StandardCharsets.UTF_8);
        BaseHeader baseHeader = BaseHeader.build(MagicVersion.V1, CmdType.CONNECT, BodyType.TEXT, EncryptionType.NON, ReqOrResp.REQ, bytes.length);
        byte[] header = baseHeader.getHeader();

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.clear();

        buffer.put(header);
        buffer.put(bytes);
        buffer.flip();
        socketChannel.write(buffer);
        log.info("发送连接请求:{}", userId);
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        sendReq();
    }

    static void sleep(long t) {
        try {
            Thread.sleep(t);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // =============================================下面的内容暂时废弃==================================================
    // =============================================下面的内容暂时废弃==================================================
    // =============================================下面的内容暂时废弃==================================================

    private static void sendContent(SocketChannel socketChannel) throws IOException, InterruptedException {
        String bodyMsg = "你好这是苹果手机!!!-" + UUID.randomUUID().toString();
        byte[] bodyBytes = bodyMsg.getBytes(StandardCharsets.UTF_8);

        BaseHeader baseHeader = BaseHeader.build(MagicVersion.V1,
                CmdType.HEARTBEAT,
                BodyType.TEXT,
                EncryptionType.NON,
                ReqOrResp.REQ, bodyBytes.length);

        byte[] header = baseHeader.getHeader();

        ByteBuffer buffer = ByteBuffer.allocate(2048);
        buffer.clear();

        buffer.put(header);
        buffer.put(bodyBytes);

        buffer.flip();
        socketChannel.write(buffer);

        /*for (int i = 0; i < header.length; i += 2) {
            buffer.put(header[i]);
            buffer.put(header[i + 1]);
            buffer.flip();
            socketChannel.write(buffer);

            Thread.sleep(1);
            buffer.clear();
        }

        for (int i = 0; i < bodyBytes.length; i++) {
            buffer.put(bodyBytes[i]);
            buffer.flip();
            socketChannel.write(buffer);

            Thread.sleep(5);
            buffer.clear();
        }*/

        log.info("消息:{}, 请求头hash:{}", bodyMsg, baseHeader.hashCode());
        log.info("发送完成, 总字节数:{}", header.length + bodyBytes.length);
    }

    public static void sendFile() throws IOException, InterruptedException {
//        String filePath = "J:\\readme.txt";
        String filePath = "J:\\aDrive.exe";
//        String filePath = "J:\\mysql-5.7.17.msi";
        File file = new File(filePath);
        if (!file.exists()) {
            log.info("{}文件不存在", filePath);
            return;
        }

        try (FileChannel fileChannel = new FileInputStream(file).getChannel();
             SocketChannel socketChannel = SocketChannel.open()) {

            socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
            Socket socket = socketChannel.socket();
//            socket.connect(new InetSocketAddress("192.168.31.79", 8090));
            socket.connect(new InetSocketAddress("127.0.0.1", 8090));
//            socketChannel.configureBlocking(false);

            while (!socketChannel.finishConnect()) {
                log.info("正在连接...");
            }
            log.info("建立连接成功");

            ByteBuffer fileNameByteBuffer = Charset.forName("UTF-8").encode(file.getName());
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            int fileNameLen = fileNameByteBuffer.remaining();
            log.info("文件名长度:{}", fileNameLen);
            buffer.clear();
            buffer.putInt(fileNameLen);

            buffer.flip();
            socketChannel.write(buffer);
            log.info("文件名长度写入完成:{}", file.getName());

            socketChannel.write(fileNameByteBuffer);
            log.info("文件名写入完成");

            long length = file.length();
            log.info("文件长度:{}", length);
            buffer.clear();
            buffer.putInt((int) length);

            buffer.flip();
            socketChannel.write(buffer);
            log.info("文件长度写入完成");

            buffer.clear();
            log.info("传输文件开始");
            int len = 0;
            long progress = 0;
            while ((len = fileChannel.read(buffer)) > 0) {
                buffer.flip();
                // 如果配置成非阻塞模式, 有可能write返回的为0, 导致有数据没有发送到服务端
                int write = socketChannel.write(buffer);
                buffer.clear();
                progress += len;
                log.info("本次发送字节数:{}, 已处理字节数:{}, 总字节数:{}", write, progress, length);
            }

            log.info("传输文件完成");
            Thread.sleep(15000);

            socketChannel.shutdownOutput();
        }
    }
}
